/* eslint-disable */
import Camera from './Camera';
import Movie from './Movie';
// import { Base64 } from 'js-base64';
import TreeMirrorClient from './TreeMirrorClient';
import deflate from './deflate.js';

function request(method, url, headers, body) {
  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest();
    req.open(method, url, true);
    req.withCredentials = true;
    headers = headers || {};
    Object.keys(headers).forEach(key => req.setRequestHeader(key, headers[key]));
    req.onload = function () {
      if (req.status === 200) {
          resolve(req.responseText);
      } else {
          reject(new Error(req.statusText));
      }
    };
    req.onerror = function () {
      reject(new Error(req.statusText));
    };
    req.send(body);
  });
}

function encrypt(data) {
  return btoa(deflate(encodeURIComponent(data)));
}



/**
 * Class responsible for recording
 * @extends Camera
 */
 export default class Recorder extends Camera {

  /**
   * Create recorder
   * @param {Object} opts 参数集合
   * @param {URL} [opts.remoteMovieUrl] 创建movie的地址
   * @param {URL} [opts.remoteFrameUrl] 创建frame的地址
   * @param {URL} [opts.base] [description] 服务器base地址，用于获取静态资源文件
   * @param {Session} [opts.session] 本次movie会话id
   * @example
   * let recorder = new cimice.Recorder({
   *   remoteMovieUrl: 'http://localhost:4040/api/movie',
   *   remoteFrameUrl: 'http://localhost:4040/api/frameStream',
   *   base: 'http://localhost:8085/',
   *   session: 'xxx-xxx-xxx-xxx',
   * });
   */
   constructor(opts = {}) {
    super(opts);
    this.session = opts.session;
    this.wid = opts.wid;
    this.api = opts.api;
    this.remoteMovieUrl = opts.remoteMovieUrl;
    this.remoteFrameUrl = opts.remoteFrameUrl;
    this.base = opts.base;
    this.lastMousePos = {
      x: 0,
      y: 0
    };
    this.lastScrollPos = {
      top: 0,
      left: 0
    };

    this.setTarget(document.documentElement);
    this.refreshEvent = {
      name: 'cimiceRefresh',
      event: new Event('cimiceRefresh'),
      handler: function (e) {},
      t: null,
      interval: 3000
    };

    this.events = ['mousemove', 'click', 'scroll', 'resize', 'keyup', 'cimiceRefresh'];
    this.eventHandlers = [];
    this.movie = null;
    this.isRecording = false;

    this.target.addEventListener(this.refreshEvent.name, this.refreshEvent.handler, false);

    const movieId = this.initMovie().getId();

    const mutationHandler = (recorder,movie,event) => {
      // this.refreshInputsValue();
      movie.addFrame(event.frame);
    };
    const recorder = this;
    this.treeMirrorClient = new TreeMirrorClient(document,{
      initialize: (rootId, children) => {
        if (!this.wid) return console.error('none wid');

        // const xhr = new XMLHttpRequest();
        // xhr.open('POST', this.remoteMovieUrl);
        // xhr.setRequestHeader("content-type", "application/json");
        // xhr.send(JSON.stringify({
        //   id: recorder.movie.getId(),
        //   rootId: rootId,
        //   children: JSON.stringify(children),
        //   base: recorder.base,
        //   time: Date.now(),
        //   session: this.session,
        // }));

        // Promise.all([
        //   request('POST', `${this.api}/website/${this.wid}/session`),
        //   request('POST', `${this.api}/movie`,
        //     {
        //       // 'Access-Control-Allow-Headers': 'content-type',
        //       'content-type': 'application/json'
        //     },
        //     JSON.stringify({
        //     id: recorder.movie.getId(),
        //     rootId: rootId,
        //     children: JSON.stringify(children),
        //     base: recorder.base,
        //     time: Date.now(),
        //     session: this.session,
        //   }))
        // ])
        // .then(res => console.log(res))
        // .catch(err => console.error(err));


        request('POST', `${this.api}/website/${this.wid}/session`)
          .then(() => {
            setTimeout(() => {
              request('POST', `${this.api}/movie`, {
                  // 'Access-Control-Allow-Headers': 'content-type',
                  'content-type': 'application/json'
                },
                JSON.stringify({
                id: recorder.movie.getId(),
                rootId: rootId,
                children: JSON.stringify(children),
                base: recorder.base,
                time: Date.now(),
                session: this.session,
              }))
              .catch(e => console.error(e));
            }, 500);
          })
          .catch(err => console.error(err));

        const streamLength = 500;

        setInterval(() => {
          const movie = this.getMovie();
          let frames = movie.getFrames();

          if(frames.length > 500) {
            const postFrames = frames.splice(0, 500);
            // const xhr = new XMLHttpRequest();
            const framesJson = JSON.stringify(postFrames);
            // TODO gzip and encrypt
            // console.log("recording " + framesJson);
            // xhr.open('POST', this.remoteFrameUrl);
            // xhr.setRequestHeader("content-type", "application/json");
            // xhr.send(JSON.stringify({frames:framesJson,movie:recorder.movie.getId(),time:frames[frames.length - 1].ts}));
            request('POST', `${this.api}/frameStream`,
            {
              // 'content-type': 'application/json'
            },
            encrypt(JSON.stringify({
              frames: framesJson,
              movie: recorder.movie.getId(),
              time: frames[frames.length - 1].ts
            })))
            .then()
            .catch();

            movie.setFrames(frames);
          }

        }, 10000);
      },
      applyChanged: (removed, addedOrMoved, attributes, text, ts) => {
        const frame = {eventType:'mutation',ts:ts, changed:[removed, addedOrMoved, attributes, text]};
        // console.log(JSON.stringify(frame));
        recorder.onEventCallback(mutationHandler,{type:'mutation',frame:frame});
      }
    });

    // Basically events are binded to the target but unfortunately not all of them
    // are easily bindable so there is a need to have a way to indicate custom listeners.
    // At the moment I will let this property private
    this.listeners = opts.listeners || {};

    // Hard-coded listeners
    if (this.target instanceof window.HTMLHtmlElement) {
      this.listeners = { scroll: window, resize: window };
    }

    /**
     * 开始录制
     *
     * @event Recorder#startRecording
     */
     this.on('startRecording', () => {
      this.isRecording = true;

      this.refreshEvent.t = setInterval(() => {
        this.target.dispatchEvent(this.refreshEvent.event);
      }, this.refreshEvent.interval);
    });

    /**
     * 结束录制
     *
     * @event Recorder#stopRecording
     */
     this.on('stopRecording', () => {
      this.isRecording = false;
    });

    /**
     * 录制中
     *
     * @event Recorder#recording
     * @property {Event} e 事件对象
     */
    // this.on('recording', (e) => {
      // const movie = this.getMovie();
      // const frames = movie.getFrames();

      // if(frames.length > 0 && !(frames.length % 50)){
      //   // const xhr = new XMLHttpRequest();
      //   const framesJson = JSON.stringify(frames);
      //   // TODO gzip and encrypt
      //   // console.log("recording " + framesJson);
      //   // xhr.open('POST', this.remoteFrameUrl);
      //   // xhr.setRequestHeader("content-type", "application/json");
      //   // xhr.send(JSON.stringify({frames:framesJson,movie:recorder.movie.getId(),time:frames[frames.length - 1].ts}));
      //   request('POST', `${this.api}/frameStream`,
      //   {
      //     // 'content-type': 'application/json'
      //   },
      //   encrypt(JSON.stringify({
      //     frames: framesJson,
      //     movie: recorder.movie.getId(),
      //     time: frames[frames.length - 1].ts
      //   })))
      //   .then()
      //   .catch();
      //   movie.setFrames([]);
      // }
    // });

    window.onbeforeunload = () => {
      const movie = this.getMovie();
      const frames = movie.getFrames();
      const xhr = new XMLHttpRequest();
      const framesJson = JSON.stringify(frames);

      request('POST', `${this.api}/frameStream`,
        {
          // 'content-type': 'application/json'
        },
        encrypt(JSON.stringify({
          frames: framesJson,
          movie: recorder.movie.getId(),
          time: frames[frames.length - 1].ts
        })))
        .then()
        .catch();
        movie.setFrames([]);

      movie.setFrames([]);
    };
  }

  /***
   * Get recorded movie
   * @returns {Movie}
   */
   getMovie() {
    return this.movie;
  }

  /***
   * Returns the array of events that will be binded to the target
   * @returns {Array<string>} Events
   */
   getEvents() {
    return this.events;
  }

  /***
   * Set an array of events that will be binded to the target. This method
   * overrides the previous set of events.
   * @param {string|Array<string>} events Event or array of events
   * @returns {this}
   * @example
   * recorder.setEvents('resize');
   * console.log(recorder.getEvents());
   * // -> ['resize']
   * recorder.setEvents(['click','mousemove','scroll']);
   * console.log(recorder.getEvents());
   * // -> ['click','mousemove','scroll']
   */
  setEvents(events) {
    const e = [].concat(events || []);
    this.events = [];
    for (let i = 0; i < e.length; i++) {
      this.events.push(e[i]);
    }
    return this;
  }

  /***
   * Returns custom listeners
   * @return {Object} listeners
   * @private
   */
  getListeners() {
    return this.listeners;
  }

  /***
   * Get listener by event, returns target in case there is no one
   * @param {string} event Event, eg. 'scroll', 'click'
   * @return {Object} listener
   * @private
   */
  getListener(event) {
    return this.listeners[event] || this.tryGetTarget();
  }

  /***
   * Create a new movie and set parameters about a target
   * @returns {Movie} Created movie
   * @private
   */
   initMovie() {
    const target = this.tryGetTarget();
    const movie = new Movie();
    const targetPos = this.getPosTarget();
    movie.top = targetPos.top;
    movie.left = targetPos.left;
    // movie.scene = Base64.encode(target.cloneNode(true).innerHTML);
    movie.scrollX = target.scrollLeft;
    movie.scrollY = target.scrollTop;
    this.movie = movie;

    return this.movie;
  }

  /***
   * Get top and left position of the target
   * @returns {Object} An object with position data, eg. {top: 100, left: 100}
   * @private
   */
   getPosTarget() {
    const rect = this.tryGetTarget().getBoundingClientRect();
    const docEl = document.documentElement;
    return {
      top: rect.top + (window.pageYOffset || docEl.scrollTop || 0),
      left: rect.left + (window.pageXOffset || docEl.scrollLeft || 0),
    };
  }

  /**
   * 开始录制
   * @return {null}
   */
  startRecording() {
    this.emit('startRecording');

    for (let i = 0; i < this.events.length; i++) {
      const event = this.events[i];
      let callback = this.onEvents[event];

      if (!callback) {
        callback = this.abstractEventCallback;
      }

      if (event == 'keyup') {
        callback = this.keyupEventCallback;
        console.log('keyup event mounted');
      }

      if (event == 'cimiceRefresh') {
        callback = this.cimiceRefreshEventCallback;
        console.log('cimiceRefresh event mounted')
      }

      if (event == 'mousemove') {
        callback = this.mousemoveEventCallback;
        console.log('mousemove event mounted')
      }

      if (event == 'click') {
        callback = this.clickEventCallback;
        console.log('click event mounted')
      }

      if (event == 'scroll') {
        callback = this.scrollEventCallback;
        console.log('scroll event mounted');
      }

      const handler = (e) => { this.onEventCallback(callback, e); };
      this.eventHandlers.splice(i, 0, handler);

      if (event == 'scroll') {
        this.getListener(event).addEventListener(event, handler, true);
      } else {
        this.getListener(event).addEventListener(event, handler);
      }

    }
  }

  /***
   * Unbind all events and return recorded data
   * @returns {Movie} Recorded movie
   */
   stopRecording() {
    for (let i = 0; i < this.events.length; i++) {
      const event = this.events[i];
      const handler = this.eventHandlers[i];

      if (handler) {
        this.getListener(event).removeEventListener(event, handler);
      }
    }
    this.emit('stopRecording');
    return this.movie;
  }

  /***
   * Method called on any triggered event
   * @param  {Function} callback Callback to use on trigger
   * @param  {Event} event Event object
   * @private
   */
   onEventCallback(callback, event) {
    this.emit('recording', event);
    this.emit(event.type, event);
    callback(this,this.movie, event);
  }

  /***
   * Abstract callback on generic event
   * @private
   */
   abstractEventCallback(recorder,movie, e) {
    const t = e.target;
    const b = t.body;
    const scrollTop = b ? (b.parentNode.scrollTop || b.scrollTop) : t.scrollTop;
    const scrollLeft = b ? (b.parentNode.scrollLeft || b.scrollLeft) : t.scrollLeft;
    movie.addFrame({
      scrollY: scrollTop,
      scrollX: scrollLeft,
      // cursorX: e.pageX,
      // cursorY: e.pageY,
      cursorX: e.pageX || recorder.lastMousePos.x,
      cursorY: e.pageY || recorder.lastMousePos.y,
      width: recorder.target.clientWidth,
      height: recorder.target.clientHeight,
      eventType: e.type,
    });
  }

  /***
   * 键盘弹起事件监听
   * @param  {[type]} recorder [description]
   * @param  {[type]} movie    [description]
   * @param  {[type]} e        [description]
   * @return {[type]}          [description]
   */
  keyupEventCallback(recorder, movie, e) {
    let focusedElement = document.activeElement;
    if (focusedElement.tagName == 'TEXTAREA') {
      focusedElement.innerHTML = focusedElement.value;
    } else {
      focusedElement.setAttribute('value', focusedElement.value);
    }

    const t = e.target;
    const b = t.body;
    const scrollTop = b ? (b.parentNode.scrollTop || b.scrollTop) : t.scrollTop;
    const scrollLeft = b ? (b.parentNode.scrollLeft || b.scrollLeft) : t.scrollLeft;
    movie.addFrame({
      scrollY: scrollTop,
      scrollX: scrollLeft,
      cursorX: e.pageX || recorder.lastMousePos.x,
      cursorY: e.pageY || recorder.lastMousePos.y,
      width: recorder.target.clientWidth,
      height: recorder.target.clientHeight,
      eventType: e.type,
      inputValue: focusedElement.value,
    });
  }

  /***
   * 页面数据刷新事件
   * @param  {[type]} recorder [description]
   * @param  {[type]} movie    [description]
   * @param  {[type]} e        [description]
   * @return {[type]}          [description]
   */
  cimiceRefreshEventCallback(recorder, movie, e) {
    recorder.refreshInputsValue();
    const t = e.target;
    const b = t.body;
    const scrollTop = b ? (b.parentNode.scrollTop || b.scrollTop) : t.scrollTop;
    const scrollLeft = b ? (b.parentNode.scrollLeft || b.scrollLeft) : t.scrollLeft;
    movie.addFrame({
      scrollY: scrollTop,
      scrollX: scrollLeft,
      cursorX: e.pageX || recorder.lastMousePos.x,
      cursorY: e.pageY || recorder.lastMousePos.y,
      width: recorder.target.clientWidth,
      height: recorder.target.clientHeight,
      eventType: e.type,
    });
  }

  /***
   * 鼠标移动事件监听
   * @param  {[type]} recorder [description]
   * @param  {[type]} movie    [description]
   * @param  {[type]} e        [description]
   * @return {[type]}          [description]
   */
  mousemoveEventCallback(recorder, movie, e) {
    const t = e.target;
    const b = t.body;
    const scrollTop = b ? (b.parentNode.scrollTop || b.scrollTop) : t.scrollTop;
    const scrollLeft = b ? (b.parentNode.scrollLeft || b.scrollLeft) : t.scrollLeft;
    movie.addFrame({
      // scrollY: scrollTop || recorder.lastScrollPos.top,
      // scrollX: scrollLeft || recorder.lastScrollPos.left,
      scrollY: recorder.lastScrollPos.top,
      scrollX: recorder.lastScrollPos.left,
      cursorX: e.pageX,
      cursorY: e.pageY,
      width: recorder.target.clientWidth,
      height: recorder.target.clientHeight,
      eventType: e.type,
    });
    recorder.lastMousePos.x = e.pageX;
    recorder.lastMousePos.y = e.pageY;
  }

  /***
   * 点击事件监听
   * @param  {[type]} recorder [description]
   * @param  {[type]} movie    [description]
   * @param  {[type]} e        [description]
   * @return {[type]}          [description]
   */
  clickEventCallback(recorder, movie, e) {
    const t = e.target;
    const b = t.body;
    const scrollTop = b ? (b.parentNode.scrollTop || b.scrollTop) : t.scrollTop;
    const scrollLeft = b ? (b.parentNode.scrollLeft || b.scrollLeft) : t.scrollLeft;
    movie.addFrame({
      scrollY: scrollTop,
      scrollX: scrollLeft,
      cursorX: e.pageX,
      cursorY: e.pageY,
      width: recorder.target.clientWidth,
      height: recorder.target.clientHeight,
      eventType: e.type,
    });
    recorder.lastMousePos.x = e.pageX;
    recorder.lastMousePos.y = e.pageY;
  }

  /***
   * 页面滚动事件监听
   * @param  {[type]} recorder [description]
   * @param  {[type]} movie    [description]
   * @param  {[type]} e        [description]
   * @return {[type]}          [description]
   */
  scrollEventCallback(recorder, movie, e) {
    const t = e.target;
    const b = t.body;
    const scrollTop = b ? (b.parentNode.scrollTop || b.scrollTop) : t.scrollTop;
    const scrollLeft = b ? (b.parentNode.scrollLeft || b.scrollLeft) : t.scrollLeft;

    if (!e.bubbles) {
      movie.addFrame({
        scrollY: recorder.lastScrollPos.top,
        scrollX: recorder.lastScrollPos.left,
        cursorX: e.pageX || recorder.lastMousePos.x,
        cursorY: e.pageY || recorder.lastMousePos.y,
        // cursorX: recorder.lastMousePos.x,
        // cursorY: recorder.lastMousePos.y,
        width: recorder.target.clientWidth,
        height: recorder.target.clientHeight,
        eventType: e.type,
        elementScrollY: e.srcElement.scrollTop,
        elementScrollX: e.srcElement.scrollLeft,
        element: recorder.treeMirrorClient.serializeNode(e.srcElement)
      });
    } else {
      recorder.lastScrollPos.top = scrollTop;
      recorder.lastScrollPos.left = scrollLeft;
      movie.addFrame({
        scrollY: scrollTop,
        scrollX: scrollLeft,
        cursorX: e.pageX || recorder.lastMousePos.x,
        cursorY: e.pageY || recorder.lastMousePos.y,
        width: recorder.target.clientWidth,
        height: recorder.target.clientHeight,
        eventType: e.type,
      });
    }
  }

  /***
   * 刷新页面所有输入源文字
   * @return {[type]} [description]
   */
  refreshInputsValue() {
    let inputs = document.getElementsByTagName('input');
    if (inputs && inputs.length) {
      for (let i = 0; i < inputs.length; i++) {
        if (inputs[i].value) {
          inputs[i].setAttribute('value', inputs[i].value);
        }
      }
    }
    let textareas = document.getElementsByTagName('textarea');
    if (textareas && textareas.length) {
      for (let i = 0; i < textareas.length; i++) {
        if (textareas[i].innerHTML) {
          textareas[i].setAttribute('value', textareas[i].innerHTML);
        }
      }
    }
  }
}


